/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "quakedef.h"
#include "particles.h"

// extended so that we can overshoot safely for interpolation
int	ramp1[] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61};
int	ramp2[] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66};
int	ramp3[] = {0x6d, 0x6b, 0x06, 0x05, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03};


emitter_t *active_emitters = NULL;
emitter_t *free_emitters = NULL;
particle_t *free_particles = NULL;


void R_AllocFreeParticles (void)
{
	int i;

	free_particles = (particle_t *) mapPool->Alloc (MAX_PARTICLES * sizeof (particle_t));

	for (i = 1; i < MAX_PARTICLES; i++)
		free_particles[i - 1].next = &free_particles[i];

	free_particles[MAX_PARTICLES - 1].next = NULL;
}


emitter_t *R_GetEmitter (const float *org, float *dvel, float *grav)
{
	emitter_t *e = NULL;

	if (!free_emitters)
	{
		// no free emitters so get a new one
		if ((free_emitters = (emitter_t *) mapPool->Alloc (sizeof (emitter_t))) != NULL)
		{
			free_emitters->next = NULL;
			free_emitters->particles = NULL;

			return R_GetEmitter (org, dvel, grav);
		}
		else
		{
			Sys_Error ("R_GetEmitter : PoolAlloc failed");
			return NULL;
		}
	}

	// get this emitter
	GetLinkFromFreeList (active_emitters, e, free_emitters);

	// copy over properties
	Vector3Copy (e->org, org);
	Vector3Copy (e->dvel, dvel);
	Vector3Copy (e->grav, grav);

	// no particles to start with and set it's spawn time
	e->particles = NULL;
	e->time = cl.time;

	return e;
}


particle_t *R_AllocParticle (emitter_t *e, int *ramptable, float ramptime, int rampdie)
{
	particle_t *p = NULL;

	if (!free_particles)
	{
		// create a new batch of free particles
		R_AllocFreeParticles ();

		// call recursively to get the first particle off the new free list
		return R_AllocParticle (e, ramptable, ramptime, rampdie);
	}

	// take this particle
	GetLinkFromFreeList (e->particles, p, free_particles);

	// set up ramps
	p->ramptable = ramptable;
	p->ramptime = ramptime;
	p->rampdie = rampdie;

	return p;
}


__inline void RandomOrigin (float *out, const float *base, int mod, int sub)
{
	out[0] = base[0] + ((rand () % mod) - sub);
	out[1] = base[1] + ((rand () % mod) - sub);
	out[2] = base[2] + ((rand () % mod) - sub);
}


/*
===============
R_InitParticles
===============
*/
void R_InitParticles (void)
{
}


/*
===============
R_ClearParticles
===============
*/
void R_ClearParticles (void)
{
	// create an initial batch of free particles
	R_AllocFreeParticles ();

	// and nothing is active yet
	active_emitters = NULL;
	free_emitters = NULL;
}


/*
===============
R_EntityParticles
===============
*/

#define NUMVERTEXNORMALS	162
extern	float	r_avertexnormals[NUMVERTEXNORMALS][3];
vec3_t	avelocities[NUMVERTEXNORMALS];
float	beamlength = 16;
vec3_t	avelocity = {23, 7, 3};
float	partstep = 0.01f;
float	timescale = 0.01f;

void R_EntityParticles (entity_t *ent)
{
	int			count;
	int			i;
	emitter_t	*e;
	particle_t	*p;
	float		angle;
	float		sr, sp, sy, cr, cp, cy;
	vec3_t		forward;
	float		dist;

	if ((e = R_GetEmitter (ent->origin, float3 (4, 4, 4), float3 (0, 0, -1))) == NULL) return;

	dist = 64;
	count = 50;

	if (!avelocities[0][0])
	{
		for (i = 0; i < NUMVERTEXNORMALS * 3; i++)
			avelocities[0][i] = (rand () & 255) * 0.01;
	}

	for (i = 0; i < NUMVERTEXNORMALS; i++)
	{
		angle = cl.time * avelocities[i][0];

		sy = sin (angle);
		cy = cos (angle);

		angle = cl.time * avelocities[i][1];

		sp = sin (angle);
		cp = cos (angle);

		angle = cl.time * avelocities[i][2];

		sr = sin (angle);
		cr = cos (angle);

		forward[0] = cp * cy;
		forward[1] = cp * sy;
		forward[2] = -sp;

		if (!(p = R_AllocParticle (e, ramp1, 10, 7))) return;

		p->die = cl.time + 0.01;
		p->color = 0x6f;

		Vector3Set (p->vel, 0, 0, 0);

		p->org[0] = ent->origin[0] + r_avertexnormals[i][0] * dist + forward[0] * beamlength;
		p->org[1] = ent->origin[1] + r_avertexnormals[i][1] * dist + forward[1] * beamlength;
		p->org[2] = ent->origin[2] + r_avertexnormals[i][2] * dist + forward[2] * beamlength;
	}
}


void R_ReadPointFile_f (void)
{
	FILE	*f;
	vec3_t	org;
	int		r;
	int		c;
	emitter_t	*e;
	particle_t	*p;
	char	name[MAX_OSPATH];

	sprintf (name, "maps/%s.pts", sv.name);

	FS_FOpenFile (name, &f);

	if (!f)
	{
		Con_Printf (PRINT_DEFAULT, "couldn't open %s\n", name);
		return;
	}

	Con_Printf (PRINT_DEFAULT, "Reading %s...\n", name);
	c = 0;

	if ((e = R_GetEmitter (vec3_origin, float3 (0, 0, 0), float3 (0, 0, 0))) == NULL) return;

	for (;;)
	{
		r = fscanf (f, "%f %f %f\n", &org[0], &org[1], &org[2]);

		if (r != 3)
			break;

		c++;

		if (!(p = R_AllocParticle (e, NULL, 0, 0)))
		{
			Con_Printf (PRINT_DEFAULT, "Not enough free particles\n");
			break;
		}

		p->die = 99999;
		p->color = (-c) & 15;
		VectorCopy (org, p->org);
		Vector3Set (p->vel, 0, 0, 0);
	}

	fclose (f);
	Con_Printf (PRINT_DEFAULT, "%i points read\n", c);
}

/*
===============
R_ParseParticleEffect

Parse an effect out of the server message
===============
*/
void R_ParseParticleEffect (void)
{
	vec3_t	org, dir;
	int		i, count, msgcount, color;

	for (i = 0; i < 3; i++) org[i] = MSG_ReadCoord ();
	for (i = 0; i < 3; i++) dir[i] = MSG_ReadChar () * (1.0 / 16);

	msgcount = MSG_ReadByte ();
	color = MSG_ReadByte ();

	if (msgcount == 255)
		count = 1024;
	else count = msgcount;

	R_RunParticleEffect (org, dir, color, count);
}

/*
===============
R_ParticleExplosion

===============
*/
void R_ParticleExplosion (vec3_t org)
{
	int			i;
	emitter_t	*e;
	particle_t	*p;

	if ((e = R_GetEmitter (org, float3 (4, 4, 4), float3 (0, 0, -1))) == NULL) return;

	for (i = 0; i < 512; i++)
	{
		if (!(p = R_AllocParticle (e, ramp1, 10, 7))) return;

		RandomOrigin (p->org, org, 32, 16);
		RandomOrigin (p->vel, vec3_origin, 512, 256);

		p->die = cl.time + 5;
		p->color = ramp1[0];
		p->ramp = rand () & 3;
	}

	if ((e = R_GetEmitter (org, float3 (-1, -1, -1), float3 (0, 0, -1))) == NULL) return;

	for (i = 0; i < 512; i++)
	{
		if (!(p = R_AllocParticle (e, ramp2, 15, 7))) return;

		RandomOrigin (p->org, org, 32, 16);
		RandomOrigin (p->vel, vec3_origin, 512, 256);

		p->die = cl.time + 5;
		p->color = ramp1[0];
		p->ramp = rand () & 3;
	}
}

/*
===============
R_ParticleExplosion2

===============
*/
void R_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength)
{
	int			i;
	emitter_t	*e;
	particle_t	*p;
	int			colorMod = 0;

	if ((e = R_GetEmitter (org, float3 (4, 4, 4), float3 (0, 0, -1))) == NULL) return;

	for (i = 0; i < 512; i++)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		p->die = cl.time + 0.3;
		p->color = colorStart + (colorMod % colorLength);
		colorMod++;

		RandomOrigin (p->org, org, 32, 16);
		RandomOrigin (p->vel, vec3_origin, 512, 256);
	}
}

/*
===============
R_BlobExplosion

===============
*/
void R_BlobExplosion (vec3_t org)
{
	int			i;
	emitter_t	*e;
	particle_t	*p;

	if ((e = R_GetEmitter (org, float3 (4, 4, 4), float3 (0, 0, -1))) == NULL) return;

	for (i = 0; i < 512; i++)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		RandomOrigin (p->org, org, 32, 16);
		RandomOrigin (p->vel, vec3_origin, 512, 256);

		p->color = 150 - 84 * (i & 1) + rand () % 6;
		p->die = cl.time + 1 + (rand () & 8) * 0.05;
	}

	if ((e = R_GetEmitter (org, float3 (-4, -4, 0), float3 (0, 0, -1))) == NULL) return;

	for (i = 0; i < 512; i++)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		RandomOrigin (p->org, org, 32, 16);
		RandomOrigin (p->vel, vec3_origin, 512, 256);

		p->color = 150 - 84 * (i & 1) + rand () % 6;
		p->die = cl.time + 1 + (rand () & 8) * 0.05;
	}
}

/*
===============
R_RunParticleEffect

===============
*/
void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
{
	int			i;
	emitter_t	*e;
	particle_t	*p;

	if (count == 1024)
	{
		R_ParticleExplosion (org);
		return;
	}

	if ((e = R_GetEmitter (org, float3 (4, 4, 4), float3 (0, 0, -0.666f))) == NULL) return;

	for (i = 0; i < count; i++)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		p->die = cl.time + 0.1 * (rand () % 5);
		p->color = (color & ~7) + (rand () & 7);

		RandomOrigin (p->org, org, 16, 8);
		Vector3Scale (p->vel, 15, dir);
	}
}


/*
===============
R_LavaSplash

===============
*/
void R_LavaSplash (vec3_t org)
{
	int			i, j, k;
	emitter_t	*e;
	particle_t	*p;
	float		vel;
	vec3_t		dir;

	if ((e = R_GetEmitter (org, float3 (4, 4, 4), float3 (0, 0, -0.666f))) == NULL) return;

	for (i = -16; i < 16; i++)
	{
		for (j = -16; j < 16; j++)
		{
			for (k = 0; k < 1; k++)
			{
				if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

				p->die = cl.time + 2 + (rand () & 31) * 0.02;
				p->color = 224 + (rand () & 7);

				dir[0] = j * 8 + (rand () & 7);
				dir[1] = i * 8 + (rand () & 7);
				dir[2] = 256;

				p->org[0] = org[0] + dir[0];
				p->org[1] = org[1] + dir[1];
				p->org[2] = org[2] + (rand () & 63);

				VectorNormalize (dir);
				vel = 50 + (rand () & 63);
				VectorScale (dir, vel, p->vel);
			}
		}
	}
}

/*
===============
R_TeleportSplash

===============
*/
void R_TeleportSplash (vec3_t org)
{
	int			i, j, k;
	emitter_t	*e;
	particle_t	*p;
	float		vel;
	vec3_t		dir;

	if ((e = R_GetEmitter (org, float3 (4, 4, 4), float3 (0, 0, -0.666f))) == NULL) return;

	for (i = -16; i < 16; i += 4)
	{
		for (j = -16; j < 16; j += 4)
		{
			for (k = -24; k < 32; k += 4)
			{
				if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

				p->die = cl.time + 0.2 + (rand () & 7) * 0.02;
				p->color = 7 + (rand () & 7);

				dir[0] = j * 8;
				dir[1] = i * 8;
				dir[2] = k * 8;

				p->org[0] = org[0] + i + (rand () & 3);
				p->org[1] = org[1] + j + (rand () & 3);
				p->org[2] = org[2] + k + (rand () & 3);

				VectorNormalize (dir);
				vel = 50 + (rand () & 63);
				VectorScale (dir, vel, p->vel);
			}
		}
	}
}


void R_RocketTrailParticles (float *start, float *end, float *vec, float len, int baseramp)
{
	emitter_t	*e;
	particle_t	*p;

	if ((e = R_GetEmitter (start, float3 (0, 0, 0), float3 (0, 0, 1))) == NULL) return;

	for (; len > 0; len -= 3)
	{
		if (!(p = R_AllocParticle (e, ramp3, 5, 5))) return;

		p->die = cl.time + 2;
		p->ramp = (rand () & 3) + baseramp;
		p->color = ramp3[(int) p->ramp];

		RandomOrigin (p->org, start, 6, 3);
		Vector3Set (p->vel, 0, 0, 0);

		Vector3Add (start, vec, start);
	}
}


void R_BloodTrailParticles (float *start, float *end, float *vec, float len, float dec)
{
	emitter_t	*e;
	particle_t	*p;

	if ((e = R_GetEmitter (start, float3 (4, 4, 4), float3 (0, 0, -1))) == NULL) return;

	for (; len > 0; len -= dec)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		p->die = cl.time + 2;
		p->color = 67 + (rand () & 3);

		RandomOrigin (p->org, start, 6, 3);
		Vector3Set (p->vel, 0, 0, 0);

		Vector3Add (start, vec, start);
	}
}


void R_TracerParticles (float *start, float *end, float *vec, float len, int basecolor)
{
	emitter_t	*e;
	particle_t	*p;
	static int	tracercount;

	if ((e = R_GetEmitter (start, float3 (0, 0, 0), float3 (0, 0, 0))) == NULL) return;

	for (; len > 0; len -= 3)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		p->die = cl.time + 0.5;
		p->color = basecolor + ((tracercount & 4) << 1);

		Vector3Copy (p->org, start);

		if (tracercount & 1)
			Vector3Set (p->vel, 30 * vec[1], 30 * -vec[0], 0);
		else Vector3Set (p->vel, 30 * -vec[1], 30 * vec[0], 0);

		Vector3Add (start, vec, start);
		tracercount++;
	}
}


void R_VoreParticles (float *start, float *end, float *vec, float len)
{
	emitter_t	*e;
	particle_t	*p;

	if ((e = R_GetEmitter (start, float3 (0, 0, 0), float3 (0, 0, 0))) == NULL) return;

	for (; len > 0; len -= 3)
	{
		if (!(p = R_AllocParticle (e, NULL, 0, 0))) return;

		p->die = cl.time + 0.3;
		p->color = 9 * 16 + 8 + (rand () & 3);

		RandomOrigin (p->org, start, 16, 8);
		Vector3Set (p->vel, 0, 0, 0);

		Vector3Add (start, vec, start);
	}
}


void R_RocketTrail (float *start, float *end, int type)
{
	float vec[3] = {end[0] - start[0], end[1] - start[1], end[2] - start[2]};
	float len = Vector3Normalize (vec);

	switch (type & 7)
	{
	case RT_ROCKETTRAIL: R_RocketTrailParticles (start, end, vec, len, 0); break;
	case RT_SMOKETRAIL: R_RocketTrailParticles (start, end, vec, len, 2); break;
	case RT_BLOOD: R_BloodTrailParticles (start, end, vec, len, 3); break;
	case RT_SLIGHTBLOOD: R_BloodTrailParticles (start, end, vec, len, 6); break;
	case RT_TRACERWIZARD: R_TracerParticles (start, end, vec, len, 52); break;
	case RT_TRACERKNIGGIT: R_TracerParticles (start, end, vec, len, 230); break;
	case RT_TRACERVORE: R_VoreParticles (start, end, vec, len); break;
	default: break;
	}
}


void R_SetParticleColour (particle_t *p, float etime)
{
	if (!p->ramptable)
	{
		// allowing a fractional colour to blend between 2 real ones :)
		int ramp = p->color;

		p->ColorLo = d_8to24table_rgba[ramp & 255];
		p->ColorHi = d_8to24table_rgba[(ramp + 1) & 255];
		p->ColorTime = p->color - ramp;
	}
	else
	{
		// this would probably be faster on the GPU but would need a much fatter vertex, shader branching or changing, and more
		// instructions (the last is OK, the first few probably not, and it's fast enough anyway so what the hey)
		float ramp = p->ramp + etime * p->ramptime;

		// test for expiry
		if (ramp >= p->rampdie)
		{
			// kill the particle, draw as alpha, and it will be removed the next frame
			p->ColorLo = p->ColorHi = 255;
			p->ColorTime = -1;
			p->die = -1;
		}
		else
		{
			// interpolate between this ramp colour and the previous one based on overshoot of integral this
			p->ColorLo = d_8to24table_rgba[p->ramptable[(int) ramp]];
			p->ColorHi = d_8to24table_rgba[p->ramptable[((int) ramp) + 1]];
			p->ColorTime = ramp - (int) ramp;
		}
	}
}


void R_RunParticleEmitters (void)
{
	emitter_t *e;
	particle_t *p;

	for (;;)
	{
		emitter_t *kill = active_emitters;

		if (kill && !kill->particles)
		{
			MoveLinkToFreeList (active_emitters, kill, free_emitters);
			continue;
		}

		break;
	}

	for (e = active_emitters; e; e = e->next)
	{
		for (;;)
		{
			emitter_t *kill = e->next;

			if (kill && !kill->particles)
			{
				MoveLinkToFreeList (e->next, kill, free_emitters);
				continue;
			}

			break;
		}

		for (;;)
		{
			particle_t *kill = e->particles;

			if (kill && kill->die < cl.time)
			{
				MoveLinkToFreeList (e->particles, kill, free_particles);
				continue;
			}

			break;
		}

		// count active particles in this emitter
		e->numparticles = 0;

		for (p = e->particles; p; p = p->next)
		{
			for (;;)
			{
				particle_t *kill = p->next;

				if (kill && kill->die < cl.time)
				{
					MoveLinkToFreeList (p->next, kill, free_particles);
					continue;
				}

				break;
			}

			// update the particle's colour
			R_SetParticleColour (p, cl.time - e->time);

			// count active particles in this emitter
			e->numparticles++;
		}

		// add this emitter to the list for drawing
		if (!e->particles) continue;
		if (!e->numparticles) continue;

		// draw particles for this emitter
		R_AddAlphaObject (e, e->org, R_BeginParticles, (alphaobjectdrawfunc_t) R_DrawParticles, R_EndParticles);
	}
}


